package com.gitee.wsl.android.ext.device

import android.Manifest.permission.ACCESS_COARSE_LOCATION
import android.Manifest.permission.ACCESS_FINE_LOCATION
import android.content.Context
import android.content.Intent
import android.location.*
import android.os.Bundle
import android.provider.Settings
import androidx.annotation.RequiresPermission
import com.gitee.wsl.android.ext.ApplicationInit.application
import timber.log.Timber
import java.io.IOException
import java.util.*
import kotlin.math.*


/**
 * <pre>
 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2018/03/21
 *     desc  : 坐标相关工具类
 * </pre>
 */


object LocationExt {
    private const val X_PI = 3.141592653589793 * 3000.0 / 180.0
    private const val A = 6378245.0
    private const val EE = 0.006693421622965943

    /**
     * BD09 坐标转 GCJ02 坐标
     *
     * @param lng BD09 坐标纬度
     * @param lat BD09 坐标经度
     * @return GCJ02 坐标：[经度，纬度]
     */
    fun bd09ToGcj02(lng: Double, lat: Double): DoubleArray {
        val x = lng - 0.0065
        val y = lat - 0.006
        val z = sqrt(x * x + y * y) - 0.00002 * sin(y * X_PI)
        val theta = atan2(y, x) - 0.000003 * cos(x * X_PI)
        val gg_lng = z * cos(theta)
        val gg_lat = z * sin(theta)
        return doubleArrayOf(gg_lng, gg_lat)
    }

    /**
     * GCJ02 坐标转 BD09 坐标
     *
     * @param lng GCJ02 坐标经度
     * @param lat GCJ02 坐标纬度
     * @return BD09 坐标：[经度，纬度]
     */
    fun gcj02ToBd09(lng: Double, lat: Double): DoubleArray {
        val z = sqrt(lng * lng + lat * lat) + 0.00002 * sin(lat * X_PI)
        val theta = atan2(lat, lng) + 0.000003 * cos(lng * X_PI)
        val bd_lng = z * cos(theta) + 0.0065
        val bd_lat = z * sin(theta) + 0.006
        return doubleArrayOf(bd_lng, bd_lat)
    }

    /**
     * GCJ02 坐标转 WGS84 坐标
     *
     * @param lng GCJ02 坐标经度
     * @param lat GCJ02 坐标纬度
     * @return WGS84 坐标：[经度，纬度]
     */
    fun gcj02ToWGS84(lng: Double, lat: Double): DoubleArray {
        if (outOfChina(lng, lat)) {
            return doubleArrayOf(lng, lat)
        }
        var dlat = transformLat(lng - 105.0, lat - 35.0)
        var dlng = transformLng(lng - 105.0, lat - 35.0)
        val radlat: Double = lat / 180.0 * PI
        var magic = sin(radlat)
        magic = 1 - EE * magic * magic
        val sqrtmagic = sqrt(magic)
        dlat = dlat * 180.0 / (A * (1 - EE) / (magic * sqrtmagic) * PI)
        dlng = dlng * 180.0 / (A / sqrtmagic * cos(radlat) * PI)
        val mglat = lat + dlat
        val mglng = lng + dlng
        return doubleArrayOf(lng * 2 - mglng, lat * 2 - mglat)
    }

    /**
     * WGS84 坐标转 GCJ02 坐标
     *
     * @param lng WGS84 坐标经度
     * @param lat WGS84 坐标纬度
     * @return GCJ02 坐标：[经度，纬度]
     */
    fun wgs84ToGcj02(lng: Double, lat: Double): DoubleArray {
        if (outOfChina(lng, lat)) {
            return doubleArrayOf(lng, lat)
        }
        var dlat = transformLat(lng - 105.0, lat - 35.0)
        var dlng = transformLng(lng - 105.0, lat - 35.0)
        val radlat: Double = lat / 180.0 * PI
        var magic = sin(radlat)
        magic = 1 - EE * magic * magic
        val sqrtmagic = sqrt(magic)
        dlat = dlat * 180.0 / (A * (1 - EE) / (magic * sqrtmagic) * PI)
        dlng = dlng * 180.0 / (A / sqrtmagic * cos(radlat) * PI)
        val mglat = lat + dlat
        val mglng = lng + dlng
        return doubleArrayOf(mglng, mglat)
    }

    /**
     * BD09 坐标转 WGS84 坐标
     *
     * @param lng BD09 坐标经度
     * @param lat BD09 坐标纬度
     * @return WGS84 坐标：[经度，纬度]
     */
    fun bd09ToWGS84(lng: Double, lat: Double): DoubleArray {
        val gcj = bd09ToGcj02(lng, lat)
        return gcj02ToWGS84(gcj[0], gcj[1])
    }


    /**
     * WGS84 坐标转 BD09 坐标
     *
     * @param lng WGS84 坐标经度
     * @param lat WGS84 坐标纬度
     * @return BD09 坐标：[经度，纬度]
     */
    fun wgs84ToBd09(lng: Double, lat: Double): DoubleArray {
        val gcj = wgs84ToGcj02(lng, lat)
        return gcj02ToBd09(gcj[0], gcj[1])
    }

    /**
     * Mercator 坐标转 WGS84 坐标
     *
     * @param lng Mercator 坐标经度
     * @param lat Mercator 坐标纬度
     * @return WGS84 坐标：[经度，纬度]
     */
    fun mercatorToWGS84(lng: Double, lat: Double): DoubleArray {
        val x = lng / 20037508.34 * 180.0
        var y = lat / 20037508.34 * 180.0
        y = 180 / PI * (2 * atan(exp(y * PI / 180.0)) - PI / 2)
        return doubleArrayOf(x, y)
    }

    /**
     * WGS84 坐标转 Mercator 坐标
     *
     * @param lng WGS84 坐标经度
     * @param lat WGS84 坐标纬度
     * @return Mercator 坐标：[经度，纬度]
     */
    fun wgs84ToMercator(lng: Double, lat: Double): DoubleArray {
        val x = lng * 20037508.34 / 180.0
        var y: Double = ln(tan((90.0 + lat) * PI / 360.0)) / (PI / 180.0)
        y = y * 20037508.34 / 180.0
        return doubleArrayOf(x, y)
    }

    private fun transformLat(lng: Double, lat: Double): Double {
        var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * sqrt(abs(lng))
        ret += (20.0 * sin(6.0 * lng * PI) + 20.0 * sin(2.0 * lng * PI)) * 2.0 / 3.0
        ret += (20.0 * sin(lat * PI) + 40.0 * sin(lat / 3.0 * PI)) * 2.0 / 3.0
        ret += (160.0 * sin(lat / 12.0 * PI) + 320 * sin(lat * PI / 30.0)) * 2.0 / 3.0
        return ret
    }

    private fun transformLng(lng: Double, lat: Double): Double {
        var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * sqrt(
            abs(lng)
        )
        ret += (20.0 * sin(6.0 * lng * PI) + 20.0 * sin(2.0 * lng * PI)) * 2.0 / 3.0
        ret += (20.0 * sin(lng * PI) + 40.0 * sin(lng / 3.0 * PI)) * 2.0 / 3.0
        ret += (150.0 * sin(lng / 12.0 * PI) + 300.0 * sin(lng / 30.0 * PI)) * 2.0 / 3.0
        return ret
    }

    private fun outOfChina(lng: Double, lat: Double): Boolean {
        return lng < 72.004 || lng > 137.8347 || lat < 0.8293 || lat > 55.8271
    }

    private const val TWO_MINUTES = 1000 * 60 * 2

    /**
     * 判断Gps是否可用
     *
     * @return `true`: 是<br></br>`false`: 否
     */
    fun isGpsEnabled(): Boolean {
        return application.locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
    }

    /**
     * 判断定位是否可用
     *
     * @return `true`: 是<br></br>`false`: 否
     */
    fun isLocationEnabled(): Boolean {
        val lm = application.getSystemService(Context.LOCATION_SERVICE) as LocationManager
        return (lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
                || lm.isProviderEnabled(LocationManager.GPS_PROVIDER))
    }

    /**
     * 打开Gps设置界面
     */
    fun openGpsSettings() {
        val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS)
        application.startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
    }

    /**
     * 设置定位参数
     *
     * @return [Criteria]
     */
    fun getCriteria(): Criteria {
        val criteria = Criteria()
        // 设置定位精确度 Criteria.ACCURACY_COARSE比较粗略，Criteria.ACCURACY_FINE则比较精细
        criteria.accuracy = Criteria.ACCURACY_FINE
        // 设置是否要求速度
        criteria.isSpeedRequired = false
        // 设置是否允许运营商收费
        criteria.isCostAllowed = false
        // 设置是否需要方位信息
        criteria.isBearingRequired = false
        // 设置是否需要海拔信息
        criteria.isAltitudeRequired = false
        // 设置对电源的需求
        criteria.powerRequirement = Criteria.POWER_LOW
        return criteria
    }

    /**
     * 根据经纬度获取地理位置
     *
     * @param latitude  纬度
     * @param longitude 经度
     * @return [Address]
     */
    fun getAddress(latitude: Double, longitude: Double): Address? {
        val geocoder = Geocoder(application, Locale.getDefault())
        try {
            val addresses: List<Address>? = geocoder.getFromLocation(latitude, longitude, 1)
            if (addresses!!.isNotEmpty()) return addresses[0]
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return null
    }

    /**
     * 根据经纬度获取所在国家
     *
     * @param latitude  纬度
     * @param longitude 经度
     * @return 所在国家
     */
    fun getCountryName(latitude: Double, longitude: Double): String? {
        val address: Address? = getAddress(latitude, longitude)
        return if (address == null) "unknown" else address.getCountryName()
    }

    /**
     * 根据经纬度获取所在地
     *
     * @param latitude  纬度
     * @param longitude 经度
     * @return 所在地
     */
    fun getLocality(latitude: Double, longitude: Double): String? {
        val address: Address? = getAddress(latitude, longitude)
        return if (address == null) "unknown" else address.getLocality()
    }

    /**
     * 根据经纬度获取所在街道
     *
     * @param latitude  纬度
     * @param longitude 经度
     * @return 所在街道
     */
    fun getStreet(latitude: Double, longitude: Double): String? {
        val address: Address? = getAddress(latitude, longitude)
        return if (address == null) "unknown" else address.getAddressLine(0)
    }

    /**
     * 是否更好的位置
     *
     * @param newLocation         The new Location that you want to evaluate
     * @param currentBestLocation The current Location fix, to which you want to compare the new one
     * @return `true`: 是<br></br>`false`: 否
     */
    fun isBetterLocation(newLocation: Location, currentBestLocation: Location?): Boolean {
        if (currentBestLocation == null) {
            // A new location is always better than no location
            return true
        }

        // Check whether the new location fix is newer or older
        val timeDelta: Long = newLocation.time - currentBestLocation.time
        val isSignificantlyNewer: Boolean = timeDelta > TWO_MINUTES
        val isSignificantlyOlder: Boolean = timeDelta < -TWO_MINUTES
        val isNewer = timeDelta > 0

        // If it's been more than two minutes since the current location, use the new location
        // because the user has likely moved
        if (isSignificantlyNewer) {
            return true
            // If the new location is more than two minutes older, it must be worse
        } else if (isSignificantlyOlder) {
            return false
        }

        // Check whether the new location fix is more or less accurate
        val accuracyDelta = (newLocation.accuracy - currentBestLocation.accuracy) as Int
        val isLessAccurate = accuracyDelta > 0
        val isMoreAccurate = accuracyDelta < 0
        val isSignificantlyLessAccurate = accuracyDelta > 200

        // Check if the old and new location are from the same provider
        val isFromSameProvider =
            isSameProvider(newLocation.provider, currentBestLocation.provider)

        // Determine location quality using a combination of timeliness and accuracy
        if (isMoreAccurate) {
            return true
        } else if (isNewer && !isLessAccurate) {
            return true
        } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
            return true
        }
        return false
    }

    /**
     * 是否相同的提供者
     *
     * @param provider0 提供者1
     * @param provider1 提供者2
     * @return `true`: 是<br></br>`false`: 否
     */
    fun isSameProvider(provider0: String?, provider1: String?): Boolean {
        return if (provider0 == null) {
            provider1 == null
        } else provider0 == provider1
    }

}

class LocationRegisterManager {

    //ar listener: OnLocationChangeListener?=null
    var myLocationListener: MyLocationListener?=null

    /**
     * 注册
     *
     * 使用完记得调用[.unregister]
     *
     * 需添加权限 `<uses-permission android:name="android.permission.INTERNET" />`
     *
     * 需添加权限 `<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />`
     *
     * 需添加权限 `<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />`
     *
     * 如果`minDistance`为0，则通过`minTime`来定时更新；
     *
     * `minDistance`不为0，则以`minDistance`为准；
     *
     * 两者都为0，则随时刷新。
     *
     * @param minTime     位置信息更新周期（单位：毫秒）
     * @param minDistance 位置变化最小距离：当位置距离变化超过此值时，将更新位置信息（单位：米）
     * @param listener    位置刷新的回调接口
     * @return `true`: 初始化成功<br></br>`false`: 初始化失败
     */
    @RequiresPermission(ACCESS_FINE_LOCATION)
    fun register(
        minTime: Long,
        minDistance: Float,
        listener: OnLocationChangeListener
    ): Boolean {
        val mLocationManager = application.locationManager
        if (!mLocationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
            && !mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)
        ) {
            Timber.d("无法定位，请打开定位服务")
            return false
        }
        val provider: String = mLocationManager.getBestProvider(LocationExt.getCriteria(), true)?: return false
        val location: Location? =  mLocationManager.getLastKnownLocation(provider)
        if (location != null) listener.getLastKnownLocation(location)
        this.myLocationListener = MyLocationListener(listener)
        mLocationManager.requestLocationUpdates(
            provider,
            minTime,
            minDistance,
            myLocationListener!!
        )
        return true
    }

    /**
     * 注销
     */
    @RequiresPermission(ACCESS_COARSE_LOCATION)
    fun unregister() {
        myLocationListener?.let {
            application.locationManager.removeUpdates(it)
            myLocationListener = null
        }
    }

    companion object{
        @RequiresPermission(ACCESS_FINE_LOCATION)
        fun register(
            minTime: Long,
            minDistance: Float,
            listener: OnLocationChangeListener
        ): LocationRegisterManager?{
            val registerManager = LocationRegisterManager()
            if(registerManager.register(minTime, minDistance, listener))
                return registerManager
            return null
        }
    }
}

class MyLocationListener(val mListener: OnLocationChangeListener?) : LocationListener {
    /**
     * 当坐标改变时触发此函数，如果Provider传进相同的坐标，它就不会被触发
     *
     * @param location 坐标
     */
    override fun onLocationChanged(location: Location) {
        mListener?.onLocationChanged(location)
    }


    /**
     * provider的在可用、暂时不可用和无服务三个状态直接切换时触发此函数
     *
     * @param provider 提供者
     * @param status   状态
     * @param extras   provider可选包
     */
    override fun onStatusChanged(provider: String, status: Int, extras: Bundle) {

        mListener?.onStatusChanged(provider, status, extras)

        when (status) {
            LocationProvider.AVAILABLE ->Timber.d( "当前GPS状态为可见状态")
            LocationProvider.OUT_OF_SERVICE -> Timber.d(  "当前GPS状态为服务区外状态")
            LocationProvider.TEMPORARILY_UNAVAILABLE -> Timber.d(  "当前GPS状态为暂停服务状态")
        }
    }

    /**
     * provider被enable时触发此函数，比如GPS被打开
     */
    override fun onProviderEnabled(provider: String) {}

    /**
     * provider被disable时触发此函数，比如GPS被关闭
     */
    override fun onProviderDisabled(provider: String) {}
}

interface OnLocationChangeListener {
    /**
     * 获取最后一次保留的坐标
     *
     * @param location 坐标
     */
    fun getLastKnownLocation(location: Location)

    /**
     * 当坐标改变时触发此函数，如果Provider传进相同的坐标，它就不会被触发
     *
     * @param location 坐标
     */
    fun onLocationChanged(location: Location)

    /**
     * provider的在可用、暂时不可用和无服务三个状态直接切换时触发此函数
     *
     * @param provider 提供者
     * @param status   状态
     * @param extras   provider可选包
     */
    fun onStatusChanged(provider: String, status: Int, extras: Bundle?) //位置状态发生改变
}

val globalLocationManager:LocationManager by lazy { application.locationManager }

val Context.locationManager:LocationManager
    get() = getSystemService(Context.LOCATION_SERVICE) as LocationManager